package tr3dtris.game;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
import tr3dtris.game.models.Cell;
import tr3dtris.game.models.pieces.AbstractPiece;
import tr3dtris.game.models.pieces.PieceFactory;
import tr3dtris.game.models.pieces.PieceOutOfGridException;

/**
 * Class that manages the whole game logic like piece creation and destruction,
 * point management, row deletion, special feature, etc.
 * @author Luciano Mammino <loige@hotmail.com>
 * @author Andrea Mangano <kapitan0@hotmail.it>
 * @version 0.1
 * @since 0.1
 */
public class GameLogic
{

    /**
     * Timer interval standard duration
     */
    private static final int TIMER_INTERVAL = 600;

    /**
     * Reference to the connected <Game> class
     */
    protected Game game;

    /**
     * Timer used to handle synchronous game events
     * (Blocks falling, etc...)
     */
    private Timer timer;

    /**
     * The <PieceFactory> used to simplify the creation of all the pieces needed
     * in the game
     */
    private PieceFactory pieceFactory;

    /**
     * Constructor. Creates a new <GameLogic> instance for a given game
     * @param game
     */
    public GameLogic(Game game)
    {
        this.game = game;
        this.timer = new Timer(TIMER_INTERVAL, new TimerHandler());
        this.pieceFactory = new PieceFactory(this.game.getGrid());
    }

    /**
     * Starts the game;
     */
    public void start()
    {
        this.game.setPlaying(true);
        this.timer.start();

        this.game.setCurrentPiece(this.createNewPiece());
        this.game.setNextPiece(this.createNewPiece());
        this.timer.start();

        this.game.refreshAllDisplay();
    }

    /**
     * Pauses the game
     */
    public void pause()
    {
        if(this.game.isPaused())
        {
            this.game.setPaused(false);
            this.game.setPlaying(true);
            this.timer.start();
        }
        else
        {
            this.game.setPaused(true);
            this.game.setPlaying(false);
            this.timer.stop();
        }
        this.game.refreshAllDisplay();
    }

    /**
     * restarts the game
     */
    public void restart()
    {
        this.game.getGrid().clear();
        this.game.setPoints(0);
        this.game.setLinesErased(0);
        this.game.setGameOver(false);
        this.start();
    }

    /**
     * Creates a new random piece
     * @return
     */
    private AbstractPiece createNewPiece()
    {
        AbstractPiece p = this.pieceFactory.createRandomBasicPiece();
        p.setY(0 - p.getCenter().y);
        p.setX(this.game.getGrid().getWidth()/2 - p.getCenter().x);

        return p;
    }

    /**
     * @return the timer
     */
    public Timer getTimer()
    {
        return timer;
    }

    /**
     * @return the pieceFactory
     */
    public PieceFactory getPieceFactory() {
        return pieceFactory;
    }

    /**
     * Callback function called from the timer listener
     */
    private void timerTick()
    {
        if(this.game.getCurrentPiece().canMoveDown())
            this.game.getGameActions().moveCurrentPieceDown();
        else
            this.dropPiece();
    }

    /**
     * Called when a piece needs to be dropped and stored into the parent grid
     */
    private void dropPiece()
    {
        try
        {
            this.game.getCurrentPiece().storeIntoParentGrid();
        }
        catch(PieceOutOfGridException e)
        {
            this.gameOver();
        }
        this.game.setCurrentPiece(this.game.getNextPiece());
        this.game.setNextPiece(this.createNewPiece());
        this.checkFilledLines();
    }

    /**
     * Checks for filled lines
     */
    private void checkFilledLines()
    {
        int linesErased = 0;
        for(int y=0; y < this.game.getGrid().getHeight(); y++)
        {
            int count = 0;
            for(int x=0; x < this.game.getGrid().getWidth(); x++)
            {
                if(!this.game.getGrid().getCellAt(x, y).isEmpty())
                    count++;
                else
                    break;
            }

            if(count == this.game.getGrid().getWidth())
            {
                this.eraseLine(y);
                linesErased++;
            }
        }

        if(linesErased > 0)
        {
            this.game.addLinesErased(linesErased);
            this.game.addPoints((int)Math.pow(2, linesErased));
        }
    }

    /**
     * Erases a given line
     * @param line
     */
    private void eraseLine(int line)
    {
        //TODO check if the condition works for the first line (line 0)
        this.freeLine(line);
        for(int y = line; y > 0 ; y--)
        {
            for(int x=0; x < this.game.getGrid().getWidth(); x++)
            {
                Cell upperCell = this.game.getGrid().getCellAt(x, y - 1);
                Cell currentCell = this.game.getGrid().getCellAt(x, y);
                if(!upperCell.isEmpty())
                {
                    currentCell.setBlock(upperCell.getBlock());
                    upperCell.free();
                }
            }
        }
    }

    /**
     * Frees up a given line deleting all its blocks
     * @param line
     */
    private void freeLine(int line)
    {
        for(int x=0; x < this.game.getGrid().getWidth(); x++)
        {
            this.game.getGrid().getCellAt(x, line).free();
        }
    }

    /**
     * Sets the game status to game over
     */
    private void gameOver()
    {
        this.game.setPlaying(false);
        this.game.setGameOver(true);
        this.timer.stop();
        this.game.refreshAllDisplay();
        this.game.getGameEvents().fireGameOver(game, this.game.getPoints(), this.game.getLinesErased());
    }


    
    /**
     * Class used to manage all the timer events.
     * @author Luciano Mammino <loige@hotmail.com>
     * @author Andrea Mangano <kapitan0@hotmail.it>
     * @version 0.1
     * @since 0.1
     */
    public class TimerHandler implements ActionListener
    {

        /**
         * {@inheritDoc}
         */
        public void actionPerformed(ActionEvent e)
        {
            timerTick();
        }

    }

}
